#ifndef BARCODE_EAN13_TYPES_HPP_INCLUDED
#define BARCODE_EAN13_TYPES_HPP_INCLUDED

#include "detail/alpha_to_digit.hpp"

#include "ean13_defs.hpp"

#include <boost/array.hpp>
#include <boost/cstdint.hpp>

#include <numeric>
#include <algorithm>
#include <string>
#include <sstream>

namespace barcode
{

struct ean13
{
private:
	typedef boost::array< boost::uint8_t, _ean13_total_length > code_type;
	code_type _code;

public:
	typedef code_type::value_type value_type;
	typedef value_type prefix_type;

	typedef code_type::reference reference;
	typedef code_type::const_reference const_reference;

	typedef code_type::iterator iterator;
	typedef code_type::const_iterator const_iterator;

	ean13();
	explicit ean13( const std::string& string_code_ );
	ean13( prefix_type prefix_, const std::string& string_code_ );

	void prefix( prefix_type prefix_ );
	prefix_type prefix() const;

	iterator begin();
	const_iterator begin() const;

	iterator lbegin();
	const_iterator lbegin() const;

	iterator rbegin();
	const_iterator rbegin() const;

	iterator end();
	const_iterator end() const;

	reference at( std::size_t index_ );
	const_reference at( std::size_t index_ ) const;

	boost::uint8_t check_sum() const;
	bool validate() const;

	boost::uint16_t country_code() const;

	void swap( ean13& other_ );
};

ean13::ean13()
{
}

ean13::ean13( const std::string& string_code_ )
{
	throw_if< invalid_code_length >( string_code_.length() != _ean13_total_length );

	std::transform( string_code_.begin()
		, string_code_.end()
		, _code.begin()
		, detail::alpha_to_digit< char, boost::uint8_t >() );
}

ean13::ean13( prefix_type prefix_, const std::string& string_code_ )
{
	throw_if< invalid_code_length >( string_code_.length() + 1 != _ean13_total_length );

	this->prefix( prefix_ );

	std::transform( string_code_.begin()
		, string_code_.end()
		, lbegin()
		, detail::alpha_to_digit< char, boost::uint8_t >() );
}

void ean13::prefix( prefix_type prefix_ )
{
	_code[ 0 ] = prefix_;
}

ean13::prefix_type
ean13::prefix() const
{
	return _code.front();
}

ean13::iterator ean13::begin()
{
	return _code.begin();
}

ean13::iterator ean13::end()
{
	return _code.end();
}

ean13::const_iterator ean13::begin() const
{
	return _code.begin();
}

ean13::const_iterator ean13::end() const
{
	return _code.end();
}

ean13::iterator ean13::lbegin()
{
	return _code.begin() + 1;
}

ean13::const_iterator ean13::lbegin() const
{
	return _code.begin() + 1;
}

ean13::iterator ean13::rbegin()
{
	return _code.begin() + 1 + _ean13_left_length;
}

ean13::const_iterator ean13::rbegin() const
{
	return _code.begin() + 1 + _ean13_left_length;
}

ean13::reference ean13::at( std::size_t index_ )
{
	return _code.at( index_ );
}

ean13::const_reference ean13::at( std::size_t index_ ) const
{
	return _code.at( index_ );
}

boost::uint8_t ean13::check_sum() const
{
	static boost::array< boost::uint8_t, _ean13_total_length - 1 > weights_ = { 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3 };

	int sum_ = std::inner_product( weights_.begin()
		, weights_.end()
		, _code.begin()
		, 0
		);

	return static_cast< boost::uint8_t >( 10 - sum_ % 10 );
}

bool ean13::validate() const
{
	return check_sum() == _code.back();
}

boost::uint16_t ean13::country_code() const
{
	return _code[ 0 ] * 100
		+ _code[ 1 ] * 10
		+ _code[ 2 ];
}

void ean13::swap( ean13& other_ )
{
	std::swap( _code, other_._code );
}

inline std::string to_string( const ean13& ean13_ )
{
	std::ostringstream out_;

	std::copy( ean13_.begin()
		, ean13_.end()
		, std::ostream_iterator< int >( out_ )
		);

	return out_.str();
}

inline void swap( ean13& first_, ean13& second_ )
{
	first_.swap( second_ );
}

inline bool operator==( const ean13& first_, const ean13& second_ )
{
	return std::equal( first_.begin()
		, first_.end()
		, second_.begin() );
}

inline bool operator!=( const ean13& first_, const ean13& second_ )
{
	return !( first_ == second_ );
}

struct upc
	: public ean13
{
	explicit upc( const std::string& string_code_ );
};

upc::upc( const std::string& string_code_ )
	: ean13( 0, string_code_ )
{
}

}

#endif //BARCODE_EAN13_TYPES_HPP_INCLUDED
